import 'dart:async';
import 'dart:convert';

import 'package:im_app/im/socket/router/InitRouter.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import '../service/LocalSeqCreater.dart';
import '../service/LoginService.dart';
import '../service/Path.dart';
import '../util/LoggerUtil.dart';
import 'ImWebSocketable.dart';
import 'dto/base/Resp.dart';
import 'dto/business/RpcData.dart';
import 'dto/business/SendData.dart';
import 'exception/RpcException.dart';

class ImWebSocket extends ImWebSocketable {

  final String _websokcetUrl;
  WebSocketChannel? webSocketChannel = null;
  final Map<String, dynamic> sendDataMap = {};
  final Map<String, dynamic> session = {};

  bool _isReconnection = false;                           // 是否重连
  bool _isClosed = false;                                 // 是否已经关闭

  // 心跳检测定时器
  Timer? _heartTimer = null;

  final _logger = createLogger();

  ImWebSocket(this._websokcetUrl) {}

  @override
  Future<void> start() async {
    await _connect();
  }

  @override
  Future<RpcData> sendData(RpcData rpcData, Function? function, int? timeout) async {
    if (_isClosed) throw RpcException('连接已经关闭');
    if (webSocketChannel == null || webSocketChannel?.closeReason != null) {
      await _connect();
      _logger.i("websocket reconnected!!!", time: DateTime.now());

      try {
        var loginService = LoginService();
        _isReconnection = true;
        await loginService.reconnection();

      } catch (error) {
        _isReconnection = false;
        _logger.e("websocket reconnection error", time: DateTime.now(), error: error);
        throw Exception(error);
      }

      startTimer();
      _logger.i("websocket reconnection success!!!", time: DateTime.now());
    }

    _ImSendData sendData = _ImSendData(rpcData: rpcData, decodeFunction: function);
    sendMessage(webSocketChannel, sendDataMap, session, sendData, _noRespCallback);
    try {
      return sendData.completer.future.timeout(Duration(seconds: timeout??60));
    } on TimeoutException catch(e){
      String seq = rpcData.extDataMap["localSeq"].toString();
      sendDataMap.remove(seq);
      throw e;
    }
  }

  @override
  void callbacks() {
    var callbackMap = <String, CallbackData>{};
    var initRouter = InitRouter();
    initRouter.init(callbackMap);

    callbackMap.forEach((path, callbackData){
      callback(path, callbackData.decodeFunction, callbackData.doFunction);
    });
  }

  void callback(String path, Function decodeFunction, Function doFunction) async {
    var callBackData = _ImCallBackData(path: path, decodeFunction: decodeFunction, serviceFunction: doFunction);
    sendDataMap[path] = callBackData;
  }

  @override
  void close() {
    _isClosed = true;
    webSocketChannel?.sink?.close();
    _heartTimer?.cancel();
    _logger.i("websocket close end!!!!");
  }

  Future<bool> _connect() async {
    if (webSocketChannel != null && webSocketChannel?.closeReason == null) {
      await webSocketChannel?.sink.close(-1, 'byte');

      _logger.i("websocket closed result: true", time: DateTime.now());
    }

    webSocketChannel = await websocketListener(_websokcetUrl, sendDataMap, session, dataCallBack);
    startTimer();

    callbacks();
    _logger.i("websocket client start!!!");

    return true;
  }

  void _noRespCallback(SendData sendData) {
    if (sendData is _ImSendData) {
      var imSendData = sendData as _ImSendData;
      _logger.i("imSendData: ${imSendData.completer}");
      imSendData.completer.complete();
    }
  }

  void dataCallBack(RpcData rpcData, Map<String, dynamic> sendDataMap) async {
    String seq = rpcData.extDataMap['localSeq'].toString();
    if (sendDataMap.containsKey(seq)) {
      var innerData = sendDataMap.remove(seq);
      _ImSendData sendData = innerData.message as _ImSendData;
      Completer completer = sendData.completer;
      completer.complete(rpcData);
    }  else { // callback
      String path = rpcData.path;
      if (!sendDataMap.containsKey(path)) {
        _logger.e("请注册router: $path, ${jsonEncode(rpcData.data)}");
        throw Exception("请注册router: $path");
      }
      // _logger.i("websocket receive server message ${path}");
      _ImCallBackData callBackData = sendDataMap[path];
      var result = await callBackData.serviceFunction(rpcData.data);
      _logger.i('websocket call result: ${jsonEncode(result)}, path: ${rpcData.path}', time: DateTime.now());
      await _sendCallbackResultData(result, rpcData);
    }
  }

  void startTimer() {
    _heartTimer?.cancel();
    const int periodicTime = 20;
    var seqCreator = DefaultSeqCreator();
    _heartTimer = Timer.periodic(const Duration(seconds: periodicTime), (timer){
      var now = DateTime.now();
      var d1 = now.difference(lastedReceiveDataTime).inSeconds;
      var d2 = now.difference(lastedWriteDataTime).inSeconds;
      if ( d1> periodicTime-2 &&  d2> periodicTime-2) {
        _logger.i('websocket heart check start!');

        String? seq = seqCreator.create();
        RpcData rpcData = RpcData.data(ImPath.pingToServer.name, PingReq(), seq);
        sendData(rpcData, PongResp.fromJson, 60);
      } else {
        _logger.i('websocket heart check skip, $d1:$d2');
      }
    });
  }

  /// 发送callback的返回值
  _sendCallbackResultData(dynamic result, RpcData rpcData) async {
    var resultData = result;
    if (result is Future) {
      var rdata = await result;
      if (rdata != null) {
        resultData = rdata;
      } else {
        resultData = OkResp(coder: 0, successDataTime: DateTime.now());
      }
    }

    var extDataMap = <String, String>{};
    extDataMap["localSeq"] = rpcData.extDataMap['localSeq']!;
    var sendRpcData = RpcData(rpcData.path, rpcData.version, 10, extDataMap, resultData);
    SendData sendData = SendData(rpcData: sendRpcData, decodeFunction: null);
    sendMessage(webSocketChannel, sendDataMap, session, sendData, _noRespCallback);
  }
}

class _ImSendData extends SendData {
  Completer<RpcData> completer = Completer();

  _ImSendData({required super.rpcData, required super.decodeFunction});
}

class _ImCallBackData extends CallBackData {
  final Function serviceFunction;

  _ImCallBackData({required super.path, required super.decodeFunction, required this.serviceFunction});
}